﻿
//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

#include "CmdSnapWithAction.h"
#include "IPick.h"
#include "ISnap.h"
#include "IPlane.h"
#include "IUiView.h"
#include "ILine3d.h"
#include "Vector3d.h"
#include "IElement.h"
#include "IDocument.h"
#include "ActionBase.h"
#include "IModelView.h"
#include "IModelLine.h"
#include "IWorkPlane.h"
#include "ISelection.h"
#include "IPickFilter.h"
#include "IUiDocument.h"
#include "IPickTarget.h"
#include "UiSnapUtils.h"
#include "ISnapContext.h"
#include "IGraphicsNode.h"
#include "Vector3dUtils.h"
#include "ISnapSettings.h"
#include "ContainerUtil.h"
#include "UiCommonDialog.h"
#include "IGraphicsPoint.h"
#include "IGraphicsPlane.h"
#include "IPickCandidates.h"
#include "GcmpCommandState.h"
#include "IPickPointAction.h"
#include "IGraphicsCurve3d.h"
#include "IUserTransaction.h"
#include "IGraphicsNodeGroup.h"
#include "IElementModelShape.h"
#include "UiDocumentViewUtils.h"
#include "IGraphicsElementShape.h"
#include "IGraphicsNodeReference.h"
#include "IPickPointActionConfig.h"
#include "GraphicsNodeAndTransform.h"
#include "ITemporaryDimensionContext.h"
#include "ITemporaryDimensionManager.h"

#include "SnapFilterDlg.h"
#include "CommandRegister.h"
#include "SampleUIUtils.h"
#include "PickSnapCommandIds.h"
#include "CmdSnapModuleEntry.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 
#include "SnapAction\SamplePickPointAction.h"


using namespace gcmp;
using namespace Sample;

namespace Sample
{
#pragma region 捕捉过滤器和IPickPointActionConfig实现类
    // 捕捉的过滤器
    class SnapWithActionPickFilter : public IPickFilter
    {
    public:
        SnapWithActionPickFilter()
        {
        }

        virtual bool AllowElement(const gcmp::ElementId& elementId) const override
        {
            return true;
        }

        virtual bool AllowGraphicsNode(const gcmp::IGraphicsNodeReference& graphicsNodeReference) const override
        {
            return true;
        }

        virtual bool SetPickTargetOption(gcmp::IPickTarget* pPickTarget) override
        {
            pPickTarget->EnableAll();
            return true;
        }
    };

    /// IPickPointActionConfig是纯虚类，需要继承后才可以实例化
    class SamplePickPointConfigure : public IPickPointActionConfig
    {
        DEFINE_CAST_DERIVED(SamplePickPointConfigure, gcmp::IPickPointActionConfig);
    public:
        SamplePickPointConfigure() {};
        virtual ~SamplePickPointConfigure() {};

        // 用户配置自定义的正交模式
        virtual bool ConfigSnapSettings(ISnapSettings* snapSetting) override;
    };

    bool SamplePickPointConfigure::ConfigSnapSettings(ISnapSettings* pSnapSetting)
    {
        // 这里IPickPointAction设置ISnapSettings，通过回调函数可以在IPickPointAction拾取过程中修改哪些捕捉类型可用
        SnapFilterDlg* snapFilterDlg = SnapFilterDlg::Get();
        DBG_WARN_AND_RETURN_FALSE_UNLESS(snapFilterDlg, L"snapFilterDlg为空", L"GDMP", L"2023-12-30");
        snapFilterDlg->SetSnapSettings(pSnapSetting);

        int tolerance = CmdSnapModuleEntry::GetTolerance();
        pSnapSetting->SetSnapDistanceForLocalSnap(tolerance);
        pSnapSetting->SetSnapDistanceForRemoteSnap(tolerance);
        pSnapSetting->SetSnapDistanceForNonreferenceSnap(tolerance);

        return true;
    }

    /// 设置捕捉光标
    bool GetSnapWithActionMousePath(std::wstring& cursorPath)
    {
        cursorPath = L":/images/Cursor/cross_big.png";
        return true;
    }
#pragma endregion 捕捉过滤器和IPickPointActionConfig实现类

#pragma region 使用IPickPointAction的Action

    /// 用使用IPickPointAction的Action拾取点来绘制模型线
    class SnapWithActionAction : public gcmp::ActionBase
    {
    public:
        // 绘制空间线的状态
        enum class DrawLinearStatus : int32_t
        {
            Not_Started,
            Getting_StartPoint,
            Getting_EndPoint
        };
    public:
        SnapWithActionAction() :
            m_isUserCancelled(false)
        {
            m_snapFilterDlg = SnapFilterDlg::Get();
            DBG_WARN_UNLESS(m_snapFilterDlg, L"SnapFilterDlg为空", L"GDMP", L"2023-12-30");
        }

        /// 初始化Action
        virtual void InitAction(IUiView* pCurrentView) override
        {
            DBG_WARN_AND_RETURN_VOID_UNLESS(pCurrentView, L"pCurrentView为空", L"GDMP", L"2023-12-30");

            // 初始化近程和远程捕捉的图元和图形节点过滤器
            m_opLocalPickFilter = NEW_AS_OWNER_PTR(SnapWithActionPickFilter);
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_opLocalPickFilter, L"m_opLocalPickFilter为空", L"GDMP", L"2023-12-30");
            m_opRemotePickFilter = NEW_AS_OWNER_PTR(SnapWithActionPickFilter);
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_opRemotePickFilter, L"m_opRemotePickFilter为空", L"GDMP", L"2023-12-30");

            // 开始绘制模型线的起点
            EnterGettingStartPointState(pCurrentView);
        }

        /// 子Action结束，这里是IPickPointAction结束返回这里
        virtual void OnChildActionFinished(IUiView* pCurrentView, const ActionOutput& childActionReturnParam) override
        {
            // 如果结束的IPickPointAction是绘制模型线起点
            if (m_drawLinearStatus == DrawLinearStatus::Getting_StartPoint)
            {
                if (m_isUserCancelled)
                {
                    // 如果用户在画第一个点时就选择退出，那么就退出整个画线工具。
                    CleanupTempGraphicsShape();// 记得删除临时线
                    MarkFinishStatus(ActionFinishStatus::Cancelled);
                    UpdateView();
                    return;
                }
                else
                {
                    // 如果用户绘制了起点，那么进入绘制模型线终点
                    EnterGettingEndPointState(pCurrentView);
                    return;
                }
            }
            else if (m_drawLinearStatus == DrawLinearStatus::Getting_EndPoint)
            {
                // 在绘制下一条模型线前，首先删除前一条模型线的临时线
                CleanupTempGraphicsShape();
                UpdateView();

                if (m_isUserCancelled)
                {
                    // 如果用户在画第二个点时选择退出，那么先退到画第一个点的状态
                    EnterGettingStartPointState(pCurrentView);
                    return;
                }
                else if (Vector3dUtils::IsEqual(m_ptStart, m_ptEnd))
                {
                    UiCommonDialog::ShowMessageBox(GBMP_TR(L"绘制模型线"), GBMP_TR(L"起始点和终止点不能相同"), (int)UiCommonDialog::ButtonType::OK);
                    EnterGettingEndPointState(pCurrentView);
                    return;
                }
                else
                {
                    // 如果用户绘制了第二个点，那么根据起始点生成模型线
                    ElementId id = OnLineFinished(pCurrentView, m_ptStart, m_ptEnd);
                    if (id.IsValid())
                    {
                        // 如果模型线成功创建，刚结束的IPickPointAction绘制的点作为起点，开始绘制终点
                        m_ptStart = m_ptEnd;
                        EnterGettingEndPointState(pCurrentView);
                        return;
                    }
                    else
                    {
                        // 如果模型线创建失败，那么重新开始绘制起点
                        EnterGettingStartPointState(pCurrentView);
                        return;
                    }
                }
            }
        }

    private:
        /// 初始化和开始绘制点
        /// returnPoint：返回值的绘制点
        /// pFirstPoint：拾取中的第一个点，ISnapContext::SetFirstPoint()中可以用于临时尺寸的角度等
        void InitAndStartPickPointAction(Vector3d& returnPoint, Vector3d* pFirstPoint = nullptr)
        {
            // IPickPointAction的返回值结构。IPickPointAction会讲拾取到的点添加到m_pickedPoints，捕捉结果设置给m_opSnap
            PickPointExchangeData exchangeData(&returnPoint, &m_isUserCancelled, &m_pickedPoints, &m_opSnap);
            // ISnapSettings通过IPickPointActionConfig实现
            OwnerPtr<IPickPointActionConfig> opPickConfigure = NEW_AS_OWNER_PTR(SamplePickPointConfigure);
            DBG_WARN_AND_RETURN_VOID_UNLESS(opPickConfigure, L"opPickConfigure为空", L"GDMP", L"2023-12-30");

            // PickPointExchangeData中也包含捕捉的输入设置，捕捉的平面在教程中有介绍
            exchangeData.m_planeType = CmdSnapModuleEntry::GetSnapPlaneType();
            // 在画布中显示坐标输入时，m_pickPointType为TwoDimensionPoint，会显示XY输入，ThreeDimensionPoint会显示XYZ输入
            exchangeData.m_pickPointType = CmdSnapModuleEntry::GetPickPointType();

            // 创建IPickPointAction，IPickPointAction在鼠标移动时会调用回调函数SnapWithActionAction::OnPointUpdate
            gcmp::OwnerPtr<IPickPointAction> opPickPointAction = SamplePickPointAction::Create(exchangeData,
                IPickPointAction::CreateMoveCallback(&SnapWithActionAction::OnPointUpdate, this),
                ActionFinishedMode::LButtonDown, false, TransferOwnership(opPickConfigure));
            DBG_WARN_AND_RETURN_VOID_UNLESS(opPickPointAction, L"opPickPointAction为空", L"GDMP", L"2023-12-30");

            // 设置在IPickPointAction捕捉到点之前做预处理的回调函数
            opPickPointAction->SetMoveBeforeSnapCallBack(IPickPointAction::CreateMoveBeforeSnapCallBack(&SnapWithActionAction::ProcessMouseMoveBeforeSnap, this));

            // 从新创建的IPickPointAction中取出ISnapContext
            ISnapContext* pSnapContext = opPickPointAction->GetSnapContext();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pSnapContext, L"pSnapContext为空", L"GDMP", L"2023-12-30");

            // 设置近程和远程捕捉过滤器
            pSnapContext->SetFilterForLocalSnap(m_opLocalPickFilter.get());
            pSnapContext->SetFilterForRemoteSnap(m_opRemotePickFilter.get());

            // 如果SnapPlaneType使用FirstPointPlane方式，这一步必须在ISnapContext::SetFirstPoint调用之后。因为FirstPoint会作为SnapPlane的原点
            if (pFirstPoint)
            {
                pSnapContext->SetFirstPoint(pFirstPoint);
            }

            /// 初始化三维正交捕捉时的坐标系
            // UiSnapUtils用当前IUiView对ISnapContext做初始化
            IUiView* pUiView = UiDocumentViewUtils::GetCurrentUiView();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pUiView, L"pUiView为空", L"GDMP", L"2023-12-30");
            bool doesInitSuccess = UiSnapUtils::InitSnapContextFromUiView(pSnapContext, pUiView, exchangeData.m_planeType);
            DBG_WARN_AND_RETURN_VOID_UNLESS(doesInitSuccess, L"UiSnapUtils设置ISnapContext失败", L"GDMP", L"2023-12-30");

            // 可以用于测试SnapPlane和当前视图工作平面不一样时，PickPointExchangeData::m_planeType三种选项的区别
            if (CmdSnapModuleEntry::DoSetXYPlaneAsSnapPlane())
            {
                OwnerPtr<IGraphicsPlane> opXYPlane = IGraphicsPlane::Create();
                DBG_WARN_AND_RETURN_VOID_UNLESS(opXYPlane, L"opXYPlane为空", L"GDMP", L"2023-12-30");
                pSnapContext->SetSnapPlane(TransferOwnership(opXYPlane));
            }
            // 坐标系是否用前一条模型线作为X轴，这样正交捕捉会使用前一条线作为X轴
            else if (CmdSnapModuleEntry::DoRotateCoordinate())
            {
                if (m_pickedPoints.size() > 1)
                {
                    int size = (int)m_pickedPoints.size();
                    Vector3d newXDir = m_pickedPoints.at(size - 1) - m_pickedPoints.at(size - 2);

                    if (!Vector3dUtils::IsEqual(newXDir, Vector3d::Zero, Constants::MIN_MODEL_SIZE))
                    {
                        const IGraphicsPlane& pSnapPlane = pSnapContext->GetSnapPlane();
                        Vector3d newYDir;
                        // 如果和世界坐标系的Z轴夹角很小，则使用世界坐标系的Y轴去计算局部坐标系的Y轴
                        if (Vector3dUtils::GetAcuteAngle(newXDir, Vector3d::UnitZ) < Constants::ANGLE_EPS * 100)
                        {
                            newYDir = Vector3d::UnitY.Cross(newXDir);
                        }
                        else
                        {
                            newYDir = Vector3d::UnitZ.Cross(newXDir);
                        }
                        OwnerPtr<IGraphicsPlane> opGraphicsPlane = IGraphicsPlane::CreateByDirections(m_pickedPoints.at(size - 1), newXDir, newYDir);
                        pSnapContext->SetSnapPlane(TransferOwnership(opGraphicsPlane));
                    }
                }
            }

            // UiSnapUtils::InitSnapContextFromUiView初始化内容不包含三维正交捕捉的Coordinate，例程中将三维正交捕捉和SnapPlane设为一样
            const IGraphicsPlane& pSnapPlane = pSnapContext->GetSnapPlane();
            m_snapCoord = Coordinate3d(pSnapPlane.GetOrigin(), pSnapPlane.GetDirectionU(), pSnapPlane.GetDirectionV());
            pSnapContext->SetCoordinate(m_snapCoord);

            // 用于测试打开或关闭画布中的坐标输入
            if (CmdSnapModuleEntry::DoUseCoordinateInput())
            {
                OwnerPtr<IPlane> opPlane = nullptr;
                // 如果旋转了坐标系统，那么坐标输入也使用ISnapContext::SetSnapPlane中设置的局部坐标系
                if (CmdSnapModuleEntry::DoRotateCoordinate())
                {
                    if (m_pickedPoints.size() > 1)
                    {
                        const IGraphicsPlane& pSnapPlane = pSnapContext->GetSnapPlane();
                        const IPlane* pPlane = pSnapPlane.GetGmIPlane();
                        opPlane = TransferOwnershipQuickCast<IPlane>(pPlane->Clone());
                    }
                }
                opPickPointAction->StartCoordinateInput(TransferOwnership(opPlane), true);
            }

            // 用于测试为鼠标设置新样式是否生效
            if (CmdSnapModuleEntry::DoCustomizeCursor())
            {
                opPickPointAction->SetCursorPathCallBack(GetSnapWithActionMousePath);
            }

            /// 开始用IPickPointAction捕捉点
            StartChildAction(TransferOwnership(opPickPointAction));
        }

        /// 在IPickPointAction捕捉到点时，可以做预处理。这里如果选择了共线约束，那么将前面拾取的两个点形成的直线作为约束线
        void ProcessMouseMoveBeforeSnap(gcmp::IUiView *pCurrentView, const Vector3d &mousePt, ISnapContext *pSnapContext)
        {
            OwnerPtr<ISnapSettings> opSnapSettings = pSnapContext->GetSnapSettings();
            DBG_WARN_AND_RETURN_VOID_UNLESS(opSnapSettings, L"opSnapSettings为空", L"GDMP", L"2023-12-30");

            int pointCount = (int)m_pickedPoints.size();
            if (opSnapSettings->CanSnapCollinear() && pointCount > 1)
            {
                m_opCollinearCurve = ILine3d::Create(m_pickedPoints.at(pointCount - 1), m_pickedPoints.at(pointCount - 2));

                if (m_opCollinearCurve && m_opCollinearCurve->GetLength() > Constants::MIN_MODEL_SIZE)
                {
                    bool setResult = pSnapContext->SetCollinearCurve(m_opCollinearCurve.get());
                    DBG_WARN_AND_RETURN_VOID_UNLESS(setResult, L"SetCollinearCurve失败", L"GDMP", L"2023-12-30");
                }
            }
        }

        // 初始化和开始捕捉模型线的起点
        void EnterGettingStartPointState(gcmp::IUiView* pCurrentView)
        {
            m_drawLinearStatus = DrawLinearStatus::Getting_StartPoint;
            SetPromptMessage(GBMP_TR(L"请输入模型线起始点"));
            InitAndStartPickPointAction(m_ptStart);
        }

        // 初始化和开始捕捉模型线的终点
        void EnterGettingEndPointState(gcmp::IUiView* pCurrentView)
        {
            m_drawLinearStatus = DrawLinearStatus::Getting_EndPoint;
            SetPromptMessage(GBMP_TR(L"请输入模型线终止点"));
            InitAndStartPickPointAction(m_ptEnd, &m_ptStart);
        }

        // 用于在IPickPointAction鼠标移动和捕捉过程中，绘制临时线
        void OnPointUpdate(gcmp::IUiView* pCurrentView, const gcmp::Vector3d& pickedPt, const gcmp::Vector3d& mousePos)
        {
            // 在底部状态栏按照捕捉点坐标
            std::wstring tooltip = L"pickedPt: " + pickedPt.GetStringForDebug() + L"mouse: " + mousePos.GetStringForDebug();
            if (m_opSnap)
            {
                tooltip += L"捕捉成功" + m_opSnap->GetShortPrompt();
            }
            SampleUIUtils::SetStatusBarPromptMessage(tooltip);

            if (!Vector3dUtils::IsEqual(m_ptStart, pickedPt, Constants::MIN_MODEL_SIZE) && m_drawLinearStatus == DrawLinearStatus::Getting_EndPoint)
            {
                // 清空之前的临时线，绘制新的临时线
                CleanupTempGraphicsShape();
                OwnerPtr<IGraphicsElementShape> opTempGRep = IGraphicsElementShape::Create(GraphicsRenderLayer::TransientObject);
                DBG_WARN_AND_RETURN_VOID_UNLESS(opTempGRep, L"opTempGRep为空", L"GDMP", L"2023-12-30");
                OwnerPtr<ILine3d> opLine = ILine3d::Create(m_ptStart, pickedPt);
                DBG_WARN_AND_RETURN_VOID_UNLESS(opLine, L"opLine为空", L"GDMP", L"2023-12-30");
                opTempGRep->AddChild(TransferOwnership(IGraphicsCurve3d::Create(opLine.get())));

                // 创建临时尺寸
                OwnerPtr<ITemporaryDimensionContext> opTemporaryDimensionContext =
                    ITemporaryDimensionContext::CreateLinearContext(m_ptStart, pickedPt, true, true, m_snapCoord);
                DBG_WARN_AND_RETURN_VOID_UNLESS(opTemporaryDimensionContext, L"opTemporaryDimensionContext为空", L"GDMP", L"2023-12-30");
                opTemporaryDimensionContext->SetValidRangeOfAngle(Intervald::Create(0.0, 180));

                // 将临时尺寸和临时线一起绘制
                opTempGRep->AddChild(TransferOwnership(ITemporaryDimensionManager::Get()->GenerateTemporaryDimensionGraphics(opTemporaryDimensionContext.get())));
                opTempGRep->SetIsSelectable(false);
                opTempGRep->SetIsSnappable(false);

                DrawTempGraphicsElementShape(TransferOwnership(opTempGRep));
                pCurrentView->GetUiDocument()->UpdateView(); // 不要忘记更新视图
            }
        }

        // 点击鼠标时创建模型线
        ElementId OnLineFinished(IUiView* pCurrentView, const Vector3d& startPt, const Vector3d& endPt)
        {
            IDocument *pDocument = pCurrentView->GetUiDocument()->GetDbDocument();
            DBG_WARN_AND_RETURN_UNLESS(pDocument, ElementId::InvalidID, L"pDocument为空", L"GDMP", L"2023-12-30");
            OwnerPtr<IUserTransaction> opUserTransaction =
                IUserTransaction::Create(pDocument, GBMP_TR(L"绘制模型线"));
            DBG_WARN_AND_RETURN_UNLESS(opUserTransaction, ElementId::InvalidID, L"创建opUserTransaction失败", L"GDMP", L"2023-12-30");

            OwnerPtr<ILine3d> opLine3d = ILine3d::Create(m_ptStart, m_ptEnd);
            DBG_WARN_AND_RETURN_UNLESS(opLine3d, ElementId::InvalidID, L"opLine3d为空", L"GDMP", L"2023-12-30");
            IModelLine* pModelLine = IModelLine::Create(pDocument, TransferOwnership(opLine3d));
            DBG_WARN_AND_RETURN_UNLESS(pModelLine, ElementId::InvalidID, L"pModelLine为空", L"GDMP", L"2023-12-30");

            opUserTransaction->Commit();

            IElement* pElem = pModelLine->GetOwnerElement();
            if (pElem)
            {
                return pElem->GetElementId();
            }
            return ElementId::InvalidID;
        }

    private:
        gcmp::Vector3d m_ptStart;   //捕捉到的起始点
        gcmp::Vector3d m_ptEnd;     //捕捉到的终止点
        bool m_isUserCancelled;     //用户是否取消了
        std::vector<gcmp::Vector3d> m_pickedPoints; // 绘制过程中绘制的点
        DrawLinearStatus m_drawLinearStatus; //绘制模型线的状态
        Coordinate3d m_snapCoord;       // 缓存捕捉的坐标系

        gcmp::OwnerPtr<gcmp::ISnap> m_opSnap; // 这里用户返回捕捉结果

        OwnerPtr<ILine3d> m_opCollinearCurve;

        OwnerPtr<IPickFilter> m_opLocalPickFilter;
        OwnerPtr<IPickFilter> m_opRemotePickFilter;

        SnapFilterDlg* m_snapFilterDlg;
    };
#pragma endregion 使用捕捉API的Action
}

#pragma region CmdSnapWithAction
REGISTER_COMMAND(CmdSnapWithAction)

CmdSnapWithAction::CmdSnapWithAction() : CommandBase(ID_CMD_SNAP_WITH_ACTION)
{
}

CmdSnapWithAction::~CmdSnapWithAction() {}

gcmp::OwnerPtr<gcmp::IAction> CmdSnapWithAction::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    if (SnapFilterDlg* dlg = SnapFilterDlg::Get())
    {
        dlg->show();
    }
    return NEW_AS_OWNER_PTR(SnapWithActionAction);
}

// 是否在启动Command前清空之前的选择集
bool CmdSnapWithAction::ShouldClearSelectionBeforeExecution() const
{
    return true;
}

bool CmdSnapWithAction::IsEnabled() const
{
    return GcmpCommandState::IsInDocumentEnvironment() && (CmdSnapModuleEntry::GetSnapMethod() == SnapMethod::IPickPointAction);
}

bool CmdSnapWithAction::IsVisible() const
{
    return GcmpCommandState::IsInDocumentEnvironment();
}
#pragma endregion CmdSnapWithAction

